package viper.sender;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Random;
import java.util.Vector;

import javax.media.Buffer;
import javax.media.CannotRealizeException;
import javax.media.CaptureDeviceInfo;
import javax.media.CaptureDeviceManager;
import javax.media.Control;
import javax.media.Controller;
import javax.media.ControllerClosedEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoDataSourceException;
import javax.media.NoProcessorException;
import javax.media.Processor;
import javax.media.control.BufferControl;
import javax.media.control.PacketSizeControl;
import javax.media.control.TrackControl;
import javax.media.format.AudioFormat;
import javax.media.protocol.BufferTransferHandler;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;

import viper.call.common.LiveStream;
import viper.main.StateManager;
import viper.main.ViperHeader;
/***************************************************
 * VIPER.java
 * -----------------------
 * Revision: v.2.0
 * Author: Ji Kim
 * Date Created: 12/05/2009
 * Description:
 *   Preliminary sender/receiver program that captures
 *   and digitizes audio from a microphone to create
 *   an audio data stream to be used for diversity
 *   coding and transmission.
 *   Also contains debug code for default path VoIP
 *   testing and diagnostics.
 * Last Back-Up: 01/24/10
 * Revision History: *Cleaned up and updated comments
 * 					 *Added socket-level access debug
 * 					 *Simple diversification capability
 * 					 *Improved Sender/Receiver code
 * 					 *Compatibility with custom stream/DataSource
 * 
 * Based on MediaTransmitter.java demo source code
 * in JMF package. This can be found in the link
 * provided in the tutorial at:
 * http://carfield.com.hk/document/java/tutorial
 *    /jmf_tutorial.pdf
 * Also based on AVTransmit2.java and DataSourceReader.java from Java SDN.
 * -----------------------  
 ***************************************************/
public class CallSender implements Runnable {
	private ArrayList<Integer> randomRelaysTemplate;
	//-------------------Singleton structure----------------------
	//public final static CallSender INSTANCE = new CallSender();
	public CallSender(){
		//initialize relays template for use in randomizing relays
		int numRelays = 0;
		randomRelaysTemplate = new ArrayList<Integer>();
		if(StateManager.INSTANCE.getDiversityCode()==ViperHeader.CODE_232)
			numRelays = 3;
		else if(StateManager.INSTANCE.getDiversityCode()==ViperHeader.CODE_364)
			numRelays = 6;
		for(int i=0;i<numRelays;i++)
			randomRelaysTemplate.add(i);
	}
	//-------------------------------------------------------------
	//-------------------capturing variables-----------------------
	public MediaLocator mediaLocator = null;
	public DataSink dataSink = null;
	public Processor mediaProcessor = null;
	public Extractor extractor = null;

	private DataSource source = null;
	private DataSource digitizedAudioSource;

	private CaptureDeviceInfo cdi;
	
	byte[] calleeAddrBytes;
	

	//Define audio codec type
	private static final Format[] FORMATS = new Format[]{
		new AudioFormat(AudioFormat.MPEG,
				48000,
				16,
				1,
				AudioFormat.BIG_ENDIAN,
				AudioFormat.SIGNED,
				8,
				8000.0,
				Format.byteArray),
		new AudioFormat(AudioFormat.ULAW,
				44100,
				8,
				2),
		new AudioFormat(AudioFormat.GSM,
				44100,
				16,
				1),
		new AudioFormat(AudioFormat.G723,
				44100,
				Format.NOT_SPECIFIED,
				1,
				Format.NOT_SPECIFIED,
				Format.NOT_SPECIFIED,
				192,
				Format.NOT_SPECIFIED,
				Format.byteArray)
	};
	//Define output of Processor
	private static final ContentDescriptor CONTENT_DESCRIPTOR =
		new ContentDescriptor(ContentDescriptor.RAW); //we just want raw data buffers
	//-------------------------------------------------------------

	
	/* 
	 * Run is called whenever a call is started.
	 */
	public void run() {
		//Set type of audio format expected from capture device
		AudioFormat format = new AudioFormat(AudioFormat.LINEAR, //encoding type
				44100, //sampling frequency 
				16, //sample depth
				2); //channels

		// Assign first available capture device as the designated device
		Vector<?> devices = CaptureDeviceManager.getDeviceList(format);
		//System.out.println("::Retrieving list of available capture devices...");
		if(devices.size()==0)
			System.out.println("No capture device found. Please connect microphone.");			
		else{
			//get first available capture device from list of devices
			cdi = (CaptureDeviceInfo) devices.elementAt(0);
			//System.out.println("::Capture device set with the following properties: ");

			//create the datasource from the device
			try{
				source = Manager.createDataSource(cdi.getLocator());
			}catch(NoDataSourceException ndse){
				System.out.println("NoDataSourceException during CallSender.run().");
			}catch(IOException ioe){
				System.out.println("IOException during CallSender.run().");
			}

			//assign datasource to processor for audio encoding
			try{
				mediaProcessor = Manager.createProcessor(source);
			}catch(NoProcessorException npe){
				System.out.println("NoProcessorException during CallSender.run().");
			}catch(IOException ioe){
				System.out.println("IOException during CallSender.run().");
			}
			
			//wait until processor is configured before proceeding
			boolean result = waitForState(mediaProcessor, Processor.Configured);
			if(!result){
				System.out.println("Unable to configure processor.");
				return;
			}

			//set output type of processor to raw data
			mediaProcessor.setContentDescriptor(CONTENT_DESCRIPTOR);

			//get list of available tracks/streams from audio encoding processor output
			TrackControl [] tracks = mediaProcessor.getTrackControls();
			if(tracks==null||tracks.length<1){
				System.out.println("No tracks in TrackControl.");
				return;
			}

			Format [] supported;
			boolean programmed = false;
			for(int i=0;i<tracks.length;i++){
				//set any audio output tracks to encode to chosen codec (default:MPEG)
				if(tracks[i].isEnabled()){
					supported = tracks[i].getSupportedFormats();
					/** DEBUG **
				System.out.println("Track #"+i);
				for(int j=0;j<supported.length;j++)
					System.out.println(supported[j].toString());
				System.out.println("========================");
					 **  DEBUG **/
					if(supported.length>1){
						//sel = supported[0];
						//tracks[i].setFormat(sel);
						tracks[i].setFormat(FORMATS[0]);
						programmed = true;

						/*
					RCModule rcm = new RCModule();
					JavaEncoder je = new JavaEncoder();
					Packetizer p = new Packetizer();
					p.setPacketSize(80);
					try{
						tracks[i].setCodecChain(new Codec[] {rcm, je, p});
					}catch(UnsupportedPlugInException upie){
						System.out.println("UnsupportedPlugInException in CallSender.run().");
					}
						 */
					}
					else
						tracks[i].setEnabled(false);
				}
				else
					tracks[i].setEnabled(false);
			}

			if(!programmed){
				System.out.println("Unable to program tracks.");
				return;
			}

			//get the list of available controls on processor
			Control [] controls = mediaProcessor.getControls();
			if(controls==null||controls.length<1){
				System.out.println("No controls in Controls.");
				return;
			}

			//packet sizing and buffer szing controls, minimum packet size/buffering = 16ms packets
			for(int i=0;i<controls.length;i++){
				if(controls[i] instanceof BufferControl){
					System.out.println("Setting BufferControl on processor.");
					((BufferControl)controls[i]).setBufferLength(10);
				}
				else if(controls[i] instanceof PacketSizeControl){
					System.out.println("Setting PacketSizeControl on processor.");
					((PacketSizeControl)controls[i]).setPacketSize(80);
				}
			}

			//wait until processor is realized before proceeding
			result = waitForState(mediaProcessor, Controller.Realized);
			if(!result){
				System.out.println("Unable to realize processor.");
				return;
			}

			//set datasource output of processor, this is encoded audio data
			digitizedAudioSource = mediaProcessor.getDataOutput();

			//get the callee array from State manager for Extractor
			String ipString = (String)StateManager.INSTANCE.getCalleeArray()[0];
			calleeAddrBytes = UDPSender.ipStringToBytes(ipString);
			InetAddress destIP = UDPSender.ipBytesToInetAddress(
					UDPSender.ipStringToBytes(ipString)
			);
			int destPort = StateManager.VIPER_PORT;

			//Begin encoding audio stream
			try {
				mediaProcessor.start(); //Start the Processor: encode audio data by defined codec
				digitizedAudioSource.start();
				System.out.println("::Audio Encoding Started...");
				//initialize extractor object to transmit encoded audio packet blocks and diversity code
				extractor = new Extractor(digitizedAudioSource, destIP, destPort);
				//extractor.setDropRate(0.3);
				extractor.start();
				System.out.println("::Transmitting to " + destIP + 
						" on port " + destPort +"...");
			} catch (Throwable t) {
				t.printStackTrace();
			}

			System.out.println("CallSender started");
		}
	}

	/**
	 * End is called whenever a call is ended.  Make sure all the objects
	 * for capturing, and the relay and callee info is cleared for future
	 * calls.
	 */
	public void end()
	{
		try {
			source.stop();
			source.disconnect();
		} catch (IOException e) {
			e.printStackTrace();
		}
		extractor.stop();
		mediaProcessor.stop();	
		mediaLocator = null;
		dataSink = null;
		mediaProcessor = null;
		extractor = null;
		source = null;		
		
		System.out.println("stop CallSender");
	}
	
	/** Helper methods from AVTransmit2.java from Java SDN **/
	private Integer stateLock = new Integer(0);
    private boolean failed = false;
    
    Integer getStateLock() {
	return stateLock;
    }

    void setFailed() {
	failed = true;
    }
	
	private synchronized boolean waitForState(Processor p, int state) {
		p.addControllerListener(new StateListener());
		failed = false;

		// Call the required method on the processor
		if (state == Processor.Configured) {
			p.configure();
		} else if (state == Processor.Realized) {
			p.realize();
		}

		// Wait until we get an event that confirms the
		// success of the method, or a failure event.
		// See StateListener inner class
		while (p.getState() < state && !failed) {
			synchronized (getStateLock()) {
				try {
					getStateLock().wait();
				} catch (InterruptedException ie) {
					return false;
				}
			}
		}

		if (failed)
			return false;
		else
			return true;
	}
	/** End helper methods. **/

	/**
	 * @param ds
	 * @throws IOException
	 * @throws NoProcessorException
	 * @throws CannotRealizeException
	 * 
	 * For assigning this as the data source.
	 */
	public void setDataSource(DataSource ds) throws IOException,
	NoProcessorException, CannotRealizeException {

		//Construct Processor that will handle encoding with specified codec.
		//mediaProcessor = Manager.createRealizedProcessor(
		//		new ProcessorModel(ds, FORMATS, CONTENT_DESCRIPTOR));


		Control [] test = mediaProcessor.getControls();

		//Isolate the BufferControl controller from Processor and manually set buffer times.
		BufferControl bcon = (BufferControl)test[1];
		bcon.setBufferLength(16); //in ms

		System.out.println("::DEBUG, Buffer Length: "+bcon.getBufferLength());

		// Get the digitized and encoded data stream as a DataSource
		digitizedAudioSource = mediaProcessor.getDataOutput();
	}
	
	/** Inner class from AVTransmit2.java from Java SDN **/
	class StateListener implements ControllerListener {

		public void controllerUpdate(ControllerEvent ce) {

			// If there was an error during configure or
			// realize, the processor will be closed
			if (ce instanceof ControllerClosedEvent)
				setFailed();

			// All controller events, send a notification
			// to the waiting thread in waitForState method.
			if (ce instanceof ControllerEvent) {
				synchronized (getStateLock()) {
					getStateLock().notifyAll();
				}
			}
		}
	}
	/** End inner class. **/
	
	//based on DataSourceReader.java from Java SDN
	class Extractor implements BufferTransferHandler {

		//-------------------capturing variables--------------------
		DataSource ds;
		PushBufferStream streams[];
		Buffer buff;
		LiveStream ls;
		Buffer bufftemp;
		//----------------------------------------------------------

		//------------------sending variables----------------------
		DatagramSocket sock = StateManager.INSTANCE.getDatagramSocket();
		byte[][] divstore; //diversity
		int counter;
		byte[][] toSend;
		private int seqNum = 0;
		private long TotalPacketCount;
		private long TotalVoiceCount;
		//----------------------------------------------------------
		
		
		//------------------callee variables-----------------------
		InetAddress destIP;
		int destPort;
		//----------------------------------------------------------
		
		//-------------------drop rate variables--------------------
		double drate;
		double[] drateArr;
		Random rgen;
		//----------------------------------------------------------
		
		
		//-------------------relay variables------------------------
		InetAddress[] relayIP_array;
		private InetAddress [] relaysIP;
		//----------------------------------------------------------

		
		/**
		 * @param src
		 * @param ip
		 * @param port
		 * 
		 * Constructor for Extractor
		 * based on DataSourceReader.java from Java SDN
		 */
		public Extractor(DataSource src, InetAddress ip, int port){
			
			//setting capturing variables
			streams = ((PushBufferDataSource)src).getStreams(); //DataSource containing the digitized audio stream is a push-type stream
			streams[0].setTransferHandler(this); //only a single audio stream in the DataSource
			ds = src;
			buff = new Buffer();
			bufftemp = new Buffer();

			//setting callee variables
			destIP = ip;
			destPort = port;

			//setting drop rate variables
			drate = 0.0;
			drateArr = null;
			rgen = new Random();
			
			//setting Count Variables
			 TotalPacketCount= 0;
			 TotalVoiceCount= 0;
			// double[] Stats;
			
			//setting relay variables
			//set size of array holding drop rates on each path and relay IP's according to code type
			if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_111){
				relaysIP = new InetAddress[1];				
			}
			else if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_232){
				drateArr = new double[3];
				drateArr[0] = 0.00;
				drateArr[1] = 0.00;
				drateArr[2] = 0.00;
				
				divstore = new byte[2][];
				relaysIP = new InetAddress[3];
			}
			else if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_364){
				drateArr = new double[6];
				divstore = new byte[3][];
				relaysIP = new InetAddress[6];
			}			
			//save relay IP addresses into relay array
			ArrayList <Object[]> relaystemp = StateManager.INSTANCE.getRelays();
			for(int i=0;i<relaystemp.size();i++){
				String relayNameTemp = (String)(relaystemp.get(i)[0]);
				byte[] relayIPTemp = UDPSender.ipStringToBytes(relayNameTemp);
				relaysIP[i] = UDPSender.ipBytesToInetAddress(relayIPTemp);
			}
			counter = 0;

			System.out.println("::Destination Host identified as: " + destIP + "/" + port);
		}

		public PushBufferStream getStream(){
			System.out.println("getStream callleeeeddd");
			return streams[0];
		}

		public void start() {
			try {
				ds.start();
			} catch (IOException e) {
				System.err.println(e);
			}
		}

		public void stop() {
			try {
				ds.stop();
			} catch (IOException e) {
				System.err.println(e);
			}
		}

		//------implementing BufferTransferHandler------------
		/* 
		 * This gets called when there's data pushed from 
		 * the PushBufferDataSource.
		 */
		//gets socket-level access to every packet that becomes available from audio encoding stream
		public void transferData(PushBufferStream stream) {
			try {
				stream.read(buff);
			} catch (IOException e) {
				System.err.println(e);
				return;
			}
			transmit(buff);
		}
		//----------------------------------------------------

		/**
		 * @param rate
		 * Set the packet loss rate as a percentage.
		 */
		public void setDropRate(double rate){
			drate = rate;
			System.out.println("::Set Packet Loss Rate to: " + drate);
		}
		
		
		/**
		 * @param rate
		 * @return Returns true if packet isn't to be dropped, 
		 * false if it should be droppped.
		 */
		private boolean checkDrop(double rate){
			double roll = rgen.nextDouble();
			//System.out.println("::DEBUG, Packet Loss Rolled: " + roll);
			if(roll>rate)
				return true;
			else
				return false;
		}
		
		//randomizing which packets are sent on which paths per cycle
		@SuppressWarnings("unchecked")
		private int[] randomRelays(){
			int[] randomRelayArray = new int[randomRelaysTemplate.size()];
			
			ArrayList<Integer> tempcopy = (ArrayList<Integer>)randomRelaysTemplate.clone();
			//System.out.println("randomRelays size: "+tempcopy.size());
			int choice;
			int size = tempcopy.size();
			for(int i=size;i>0;i--){
				choice = rgen.nextInt(i);
				//System.out.println("choice: "+choice);
				Object tempObj = tempcopy.get(choice);
				//if(tempObj==null)
				//	System.out.println("tempObj null");
				//System.out.println("tempObj.toString(): "+tempObj.toString());
				Integer tempInt = new Integer(tempObj.toString());
				//if(tempInt==null)
				//	System.out.println("tempInt null");
				int temp = tempInt.intValue();
				//System.out.println("temp: "+temp);
				randomRelayArray[size-i] = temp; 
				tempcopy.remove(choice);
				//System.out.println(randomRelayArray[size-i]);
			}
			//random ordering of ints from 1-6 or 1-3 based on coding to specify which relay sends which packet
			return randomRelayArray;
		}

		/**
		 * Returns both the Total Packet sent as well as the Total Voice packets encoded in an array
		 * 
		 * **/
		public long[] getStatistics(){
			long[] Stats = new long [2];
			Stats[0]= TotalPacketCount;
			Stats[1] = TotalVoiceCount;
			return Stats;
		}
		/**
		 * Executes every time there's data available to be sent in 
		 * the PushBufferStream.
		 * Used to extract each Buffer in the stream and 
		 * convert it to byte[] for transmission.
		 */
		public void transmit(Buffer buffer) {
			/** Extracted Buffer/byte[] Debug Messages **/
			//System.out.println("TotalPacketCount "+ TotalVoiceCount);
			/*
			System.out.println("::DEBUG, Extracted Buffer Data:");
			System.out.println("  *Time Stamp: " + buffer.getTimeStamp());
			System.out.println("  *Sequence #: " + buffer.getSequenceNumber());
			System.out.println("  *Pertinent Data Length: " + buffer.getLength());
			System.out.println("  *Data Format: " + buffer.getFormat());
			System.out.println("  *Header: " + buffer.getHeader()); //32864
			System.out.println("  *Flags: " + buffer.getFlags());
			System.out.println("  *Actual Data Length: " + ((byte[])buffer.getData()).length);
			System.out.println("  *Offset to Pertinent Data: " + buffer.getOffset());
			*/
			
			//variables that get set every time an audio packet is sent
			byte[] store = (byte[])buffer.getData();
			byte[] toSend = new byte[buffer.getLength()];
			System.arraycopy(store,buffer.getOffset(),toSend,0,buffer.getLength());
			//System.out.println(toSend.length);
			//byte[] toSend = (byte[])buffer.getData();
			long timeStamp = buffer.getTimeStamp() / 1000000;
			//System.out.println("DEBUG: time: " + timeStamp + ", " + System.currentTimeMillis());
			byte[] timeBytes = ViperHeader.longToByteArray(timeStamp);
			
			/**
			System.out.println("=================================");
			System.out.println("Sent Buffer: "+toSend);
			System.out.println("Sent Timestamp: "+buffer.getTimeStamp());
			System.out.println("=================================");
			**/
			
			/**TRANSMIT STATS**/
			int counts = 0;
			if(TotalVoiceCount>50)
				counts = 2;
			if(TotalVoiceCount>50 && counts >1){
				byte [] payload = new byte[16];
				payload [0]= (byte)(TotalPacketCount >>> 56);
				payload [1]= (byte)(TotalPacketCount >>> 48);
				payload [2]= (byte)(TotalPacketCount >>> 40);
				payload [3]= (byte)(TotalPacketCount >>> 32);
				payload [4]= (byte)(TotalPacketCount >>> 24);
				payload [5]= (byte)(TotalPacketCount>>> 16);
				payload [6]= (byte)(TotalPacketCount >>> 8);
				payload [7]= (byte)(TotalPacketCount);
				payload [8]= (byte)(TotalVoiceCount >>> 56);
				payload [9]= (byte)(TotalVoiceCount >>> 48);
				payload [10]= (byte)(TotalVoiceCount >>> 40);
				payload [11]= (byte)(TotalVoiceCount >>> 32);
				payload [12]= (byte)(TotalVoiceCount >>> 24);
				payload [13]= (byte)(TotalVoiceCount>>> 16);
				payload [14]= (byte)(TotalVoiceCount >>> 8);
				payload [15]= (byte)(TotalVoiceCount);
				byte[] packetBytes = ViperHeader.makeCallStatisticsPacket(payload);
				StateManager.INSTANCE.sendTCPPacket(packetBytes);
				//UDPSender.sendUDP(destIP.getAddress(), packetBytes);
				TotalPacketCount =0;
				TotalVoiceCount=0;
			}
			/** TRANSMIT CODE **/
			//(1,0,1) case
			if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_101){
				byte[] packetBytes = ViperHeader.makeCallReceivePacket(timeBytes, (byte)0, toSend);
				if(checkDrop(drate)){
					//randomDelay(50,100);
					UDPSender.sendUDP(destIP.getAddress(), packetBytes);
					/**
					System.out.println("=================================");
					System.out.println("Socket Buffer: "+packetBytes);
					System.out.println("Socket TimeStamp: "+buffer.getTimeStamp());
					System.out.println("=================================");
					**/
					TotalPacketCount++;
					TotalVoiceCount++;
					//System.out.println("::DEBUG, Sent Packet...");
				}
			}

			//(1,1,1) case
			else if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_111){				
				byte[] packetBytes = ViperHeader.makeCallRelayPacket(calleeAddrBytes, timeBytes, (byte)0, toSend);
				if(checkDrop(drate)){
					UDPSender.sendUDP(relaysIP[0].getAddress(), packetBytes);
					TotalPacketCount++;
					TotalVoiceCount++;
					//System.out.println("::DEBUG, Sent Packet...");
				}
				//else
					//System.out.println("::DEBUG, Dropped Packet...");
			}
			
			//(2,3,2) case
			else if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_232){
				divstore[counter] = toSend;
				if(counter<1)
					counter++;
				else{ //we have enough for diversity coding, perform it
					//randomize relays to send out for this cycle
					int[] randomRelayArray = randomRelays();
					
					byte[][] divsend = viper.diversity.Diversity.diversify(divstore[0], divstore[1]);
					TotalVoiceCount+=2;
					//only send everything once we have enough packets to perform diversity coding
					counter = 0;
					byte [] packetBytes;
					
					// create packet for each relay with appropriate seq#, pkt type, and final destination IP
					for(int i=0;i<3;i++){
						packetBytes = ViperHeader.makeCallRelayPacket(calleeAddrBytes, timeBytes, (byte)i, divsend[i]);
						//check if we're dropping this packet based on manually specified drop rate, for debug
						if(checkDrop(drateArr[i])){
							//UDPSender.sendUDP(destIP.getAddress()/*relaysIP[0].getAddress()*/, packetBytes); //Test Case, one relay
							//send the packet to the random relay per this cycle
							UDPSender.sendUDP(relaysIP[randomRelayArray[i]].getAddress(), packetBytes);
							TotalPacketCount++;
							//System.out.println("::DEBUG, Sent Packet...");
						}
						//else
							//System.out.println("::DEBUG, Dropped Packet...");	
					}
					seqNum++;
				}
			}
			
			//(3,6,4) case
			else if(StateManager.INSTANCE.getDiversityCode() == ViperHeader.CODE_364){
				//randomize relays to send out for this cycle
				int[] randomRelayArray = randomRelays();
				
				divstore[counter] = toSend;
				if(counter<2)
					counter++;
				
				else{ //we have enough for diversity coding, perform it	
					byte[][] divsend = viper.diversity.Diversity.diversify(divstore[0], divstore[1], divstore[2]);
					TotalVoiceCount+=3;
					//only send everything once we have enough packets to perform diversity coding
					counter = 0;
					byte [] packetBytes;
					// create packet for each relay with appropriate seq#, pkt type, and final destination IP
					for(int i=0;i<6;i++){
						packetBytes = ViperHeader.makeCallRelayPacket(calleeAddrBytes, timeBytes, (byte)i, divsend[i]);
						if(checkDrop(drateArr[i])){
							UDPSender.sendUDP(relaysIP[randomRelayArray[i]].getAddress(), packetBytes);
							TotalPacketCount++;
							//System.out.println("::DEBUG, Sent Packet...");
						}
						//else
							//System.out.println("::DEBUG, Dropped Packet...");
					}
					seqNum++;
				}
			}
		}
	}
}